<?php
// +----------------------------------------------------------------------
// | MC PHP[基于ThinkPHP6.1开发]
// +----------------------------------------------------------------------
// | Copyright (c) 2020-2023 http://www.mingchangruanjian.com
// +----------------------------------------------------------------------
// | Author jokerfei [ jokerfei377@gmail.com ]
// +----------------------------------------------------------------------
// | 铭畅软件 技术部 出品
// +----------------------------------------------------------------------

namespace app\common\model;

use app\common\service\ApiReturn;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Validate;
use think\facade\Request;
use think\response\Json;

/**
 * Api请求统一调度
 * @Author jokerfei [ jokerfei377@gmail.com ]
 */
class Api
{
    //接口信息
    public static $apiInfo = null;
    //接口参数
    public static $param = null;
    //用户信息
    public static array $user = [];

    /**
     * 头部 公共参数
     * @param array $header 头部参数数组
     * @param string $alg 声明签名算法为HS256
     * @return string $typ 声明类型为jwt
     */
    private static array $header = [
        'alg' => 'HS256', //生成signature的算法 //声明签名算法为SHA256
        'typ' => 'JWT'  //声明类型为jwt
    ];

    // 初始化处理
    public static function init($hash)
    {
        $apiInfo = Apilist::getCacheInfo($hash);
        if (empty($apiInfo)) {
            return ApiReturn::r('-1'); //hash参数无效
        }

        self::$apiInfo = $apiInfo;
        if ($apiInfo['status'] < 1) {
            return ApiReturn::r('-230', '', '接口已禁用'); // 参数错误
        }

        $header = Request::header();
        $data   = self::getFormData($apiInfo["method"]);
        $check  = self::checkData($hash, $data);
        if ($check) {
            return $check;
        }
        // 验证是否需要登录
        if ($apiInfo['needlogin'] === 1) {
            //有TOKEN就获取user
            if (isset($header['user-token'])) {
                $ret = self::checkUserToken($header['user-token'],$apiInfo['needlogin']);
                if ($ret) {
                    return $ret;
                }
            }
            else {
                return ApiReturn::r('-201', [], '您还没有登录，请先登录！');
            }
        }
        elseif ($apiInfo['needlogin'] === 0) {
            //有TOKEN就获取user
            if (isset($header['user-token'])) {
                $ret = self::checkUserToken($header['user-token'],$apiInfo['needlogin']);
                if ($ret) {
                    return $ret;
                }
            }
        }
        return 0;
    }

    /**
     * 获得用户提交的参数
     * @param int $method
     * @return array
     * @author jokerfei<jokerfei377@gmail.com>
     */
    protected static function getFormData($method = 0)
    {
        $request = Request::instance();
        if ($method == 1) { // post获取数据
            return $request->post();
        }
        if ($method == 2) { // get获取数据
            return $request->get();
        }
        $_method = strtoupper($request->method()); // 用当前的请求方式 获取数据
        switch ($_method) {
            case 'GET':
                return $request->get();
            case 'POST':
                return $request->post();
            case 'DELETE':
                return $request->delete();
            case 'PUT':
                return $request->put();
            default :
                break;
        }
        return [];
    }

    /**
     * 验证参数并存储data
     * @param string $hash
     * @param array $data
     * @return int|Json
     */
    protected static function checkData($hash, $data)
    {
        $rule    = Apifields::getCacheFields($hash, 0); //获取数据库的请求字段
        $newRule = Apifields::cacheBuildValidateRule($rule);
        if ($newRule) {
            $validate = Validate::rule($newRule);
            if (!$validate->check($data)) {  //验证数据
                return ApiReturn::r('-900', [], $validate->getError()); // 参数错误
            }
        }
        $newData = [];
        foreach ($rule as $item) {
            if (!isset($data[$item['fieldname']]) || $data[$item['fieldname']] === '') {
                if ($item['default'] != '') {
                    $newData[$item['fieldname']] = $item['default'];
                }
            } else {
                $newData[$item['fieldname']] = $data[$item['fieldname']];
            }
        }
        self::$param = $newData;
        return 0;
    }

    /**
     * 验证User-token 并存入user
     * @param string $userToken
     * @return int|Json
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     * @author jokerfei<jokerfei377@gmail.com>
     */
    protected static function checkUserToken(string $userToken,$needlogin=1)
    {
        $token = cache('user_token_' . $userToken);
        if (!$token) {
            if($needlogin==0){
                return [];
            }
            return ApiReturn::r('-201', [], '您还没有登录，请先登录！');
        }
        $ret   = self::checkToken($token);
        $param = $ret['params'] ?? [];
        if ($param) {
            $userinfo = (new \app\admin\model\user\User)->field('id,user_nickname,status,mobile')->find($param->id);
            if (!$userinfo) {
                return ApiReturn::r('-203', [], '登录信息失效，请重新登录');
            }
            if ($userinfo["status"] != 1) {
                return ApiReturn::r('-203', [], '你的账户已禁用');
            }
            $userinfo_arr = $userinfo->toArray();
            self::$user   = $userinfo_arr;
        }
        else {
            $res = json_decode($ret, true);
            switch ($res['code']) {
                case 101:
                    return ApiReturn::r('-202', [], '签名不正确，请重新登录~~');
                case 102:
                    return ApiReturn::r('-202', [], '签名未到开始使用时间！');
                case 103:
                    return ApiReturn::r('-203', [], '登录信息失效，请重新登录');
                default:
                    return ApiReturn::r('-202', [], '未知错误，请联系管理员');
            }
        }
        return 0;
    }

    /**
     * 对外方法，获得user里的参数
     * @param string $field
     * @return array|string
     * @author jokerfei<jokerfei377@gmail.com>
     */
    public static function getUser($field = "")
    {
        return $field ? (self::$user[$field] ?? null) : self::$user;
    }


    /**
     * 创建 token
     * @param array $data 必填 自定义参数数组
     * @param integer $exp_time 必填 token过期时间 单位:秒 例子：7200=2小时
     * @param string $scopes 选填 token标识，请求接口的token
     * @return string
     */
    public static function createToken($data = [], $exp_time = 0, $scopes = "")
    {

        //JWT标准规定的声明，但不是必须填写的；
        //iss: jwt签发者
        //sub: jwt所面向的用户
        //aud: 接收jwt的一方
        //exp: jwt的过期时间，过期时间必须要大于签发时间
        //nbf: 定义在什么时间之前，某个时间点后才能访问
        //iat: jwt的签发时间
        //jti: jwt的唯一身份标识，主要用来作为一次性token。
        //公用信息
        try {
            $key  = "L5gROvK7jbwRmoib4ZJ9zGSCx0c5NeuG";
            $time = time(); //当前时间
            if ($scopes) {
                $token['scopes'] = $scopes; //token标识，请求接口的token
            }
            if (!$exp_time) {
                $exp_time = 604800;//默认=7天过期,暂时没启用refresh刷新token
            }

            if ($data) {
                $token['data'] = $data; //自定义参数
            }
            $token = [
                'iss'    => 'MINGCHANG', //签发者 可选
                'iat'    => $time, //签发时间
                'nbf'    => $time, //(Not Before)：某个时间点后才能访问，比如设置time+30，表示当前时间30秒后才能使用
                'scopes' => $scopes, //token标识，请求接口的token
                'exp'    => $time + $exp_time, //token过期时间,这里设置2个小时
                'params' => $data
            ];
            return JWT::encode($token, $key, 'HS256', 'mingchang'); //返回给客户端token信息

        } catch (\Firebase\JWT\ExpiredException $e) {  //签名不正确
            $returndata['code'] = "104";//101=签名不正确
            $returndata['msg']  = $e->getMessage();
            $returndata['data'] = "";//返回的数据
            return json_encode($returndata); //返回信息
        } catch (\Exception $e) {  //其他错误
            $returndata['code'] = "199";//199=签名不正确
            $returndata['msg']  = $e->getMessage();
            $returndata['data'] = "";//返回的数据
            return json_encode($returndata); //返回信息
        }
    }

    /**
     * 验证token是否有效,默认验证exp,nbf,iat时间
     * @param string $jwt 需要验证的token
     * @return array|string
     */
    public static function checkToken($jwt)
    {
        $key = "L5gROvK7jbwRmoib4ZJ9zGSCx0c5NeuG";

        try {
            JWT::$leeway = 60;//当前时间减去60，把时间留点余地
            $keyArr      = new Key($key, 'HS256');//HS256方式，这里要和签发的时候对应
            $decoded     = JWT::decode($jwt, $keyArr);
            return (array)$decoded; //返回信息

        } catch (\Firebase\JWT\SignatureInvalidException $e) {  //签名不正确
            $returndata['code'] = "101";//101=签名不正确
            $returndata['msg']  = $e->getMessage();
            $returndata['data'] = "";//返回的数据
            return json_encode($returndata); //返回信息
        } catch (\Firebase\JWT\BeforeValidException $e) {  // 签名在某个时间点之后才能用
            $returndata['code'] = "102";//102=签名不正确
            $returndata['msg']  = $e->getMessage();
            $returndata['data'] = "";//返回的数据
            return json_encode($returndata); //返回信息
        } catch (\Firebase\JWT\ExpiredException $e) {  // token过期
            $returndata['code'] = "103";//103=签名不正确
            $returndata['msg']  = $e->getMessage();
            $returndata['data'] = "";//返回的数据
            return json_encode($returndata); //返回信息
        } catch (\Exception $e) {  //其他错误
            $returndata['code'] = "199";//199=签名不正确
            $returndata['msg']  = $e->getMessage();
            $returndata['data'] = "";//返回的数据
            return json_encode($returndata); //返回信息
        }
        //Firebase定义了多个 throw new，我们可以捕获多个catch来定义问题，catch加入自己的业务，比如token过期可以用当前Token刷新一个新Token
    }
}
